Многомерные статические массивы

В си, наряду с одномерными, существуют и многомерные массивы. Например, двумерный массив: его можно представлять как массив массивов, или как матрицу. Размерность массива может быть и больше: трёхмерные, четырёхмерные и т.д.

Синтаксис остаётся прежним, добавляется только новая размерность

<тип> <имя>[размерность1][размерность2]...;

Например, двумерный массив

int a[2][3];

Трёхмерный массив

int a[3][4][5];

Доступ до элементов массива осуществляется также, как и в одномерном массиве

#define _CRT_SECURE_NO_WARNINGS

#include <conio.h>
#include <stdio.h>

#define SIZE 5

void main() {
    int M[3][3];
    unsigned i, j;

    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            M[i][j] = i * j;
        }
    }

    do {
        printf("enter indexes:\n");
        scanf("%d", &i);
        scanf("%d", &j);
        if (i < 3 && j < 3) {
            printf("M[%d][%d] == %d\n", i, j, M[i][j]);
        } else {
            break;
        }
    } while (1);

    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            printf("\t%d", M[i][j]);
        }
        printf("\n");
    }
    _getch();
}

Особенностью является то, что по своему строению многомерный массив является обыкновенным, "одномерным", массивом. Все элементы расположены друг за другом. Доступ до элемента a[i][j] – по существу сдвиг на i*число столбцов + j. В двумерном массиве, таким образом, элементы расположены "по рядам", в трёхмерном - "по слоям", внутри которых элементы расположены "по рядам" и т.д.

В связи с этим, при начальной инициализации опускать размерность можно только в первых квадратных скобках:

int a[][3] = {0};
int b[][5][25] = {0};

Компилятор будет знать в таком случае сдвиг, необходимый для доступа к элементу.

С этим связаны и особенности начальной инициализации. Так как многомерный массив по сути одномерный, то его начальную инициализацию можно провести так

int a[2][3] = {1, 2, 3, 4, 5, 6};

Можно опустить первую размерность

int a[][2][3] = {1, 2, 3, 34, 5, 6, 7, 8, 9, 10, 11, 12};

Можно с помощью фигурных скобок сделать данные более удобными для чтения

int a[2][3] = {{1, 2, 3}, {4, 5, 6}}

или

int a[][3][4] =
{{{1, 2, 3, 4}, {2, 4, 6, 8}, {3, 6, 9, 12}},
 {{1, 2, 3, 4}, {2, 4, 6, 8}, {3, 6, 9, 12}},
 {{1, 2, 3, 4}, {2, 4, 6, 8}, {3, 6, 9, 12}}};

Также, как и в одномерных массивах, если заявлено данных больше, чем указано при инициализации, то оставшиеся заполняются нулями. Например, единичная матрица 3 на 3

int zero3[3][3] = {{1}, {0, 1}, {0, 0, 1}};

Из того, что многомерный массив является одномерным по структуре, вытекают некоторые интересные свойства. Например, доступ до элемента может быть осуществлён через его порядковый номер a[i][j] === a[0][i*число столбцов + j] и т.д.

Примеры

1. Отсортируем двумерный массив методом пузырька. Для сортировки обычно используется два подхода - превращение двумерного массива в одномерный, сортировка, обратно превращение одномерного в двумерный, либо запутанное обращение к элементам через индекс. Можно сделать всё проще: работать с многомерным массивом как с одномерным

#include <conio.h>
#include <stdio.h>

#define ROWS 4
#define COLS 3

void main() {
    int a[ROWS][COLS] =
        {{1, 4, 5},
         {2, 6, 8},
         {1, 0, 9},
         {4, 2, 8}};
    int i, j, tmp, flag;

    do {
        flag = 0;
        for (i = 1; i < ROWS*COLS; i++) {
            if (a[0][i] < a[0][i-1]) {
                tmp = a[0][i];
                a[0][i] = a[0][i-1];
                a[0][i-1] = tmp;
                flag = 1;
            }
        }
    } while(flag);

    for (i = 0; i < ROWS; i++) {
        for (j = 0; j < COLS; j++) {
            printf("%3d", a[i][j]);
        }
        printf("\n");
    }

    _getch();
}

Замечание: по стандарту явно такое поведение явно не определено.

2. Даны координаты x и y точки, полученные в ходе фотосъёмки. Известно, сколько кадров в секунду делала камера. Вычислить скорость в каждый момент времени и среднюю скорость за всё время.

#include <conio.h>
#include <stdio.h>
#include <math.h>

#define SIZE 10

void main() {
    float a[2][SIZE] =
        {{1.03, 1.52, 2.11, 2.53, 3.08, 3.48,  3.98,  4.51,  5.02,  5.17},
         {1.03, 2.45, 4.13, 5.64, 7.22, 8.73, 10.32, 11.75, 13.28, 14.87}};
    float velocity[SIZE-1];
    float speed = 0.0;
    float vx, vy;
    float dt = 0.1;
    int i;

    for (i = 0; i < SIZE-1; i++) {
        vx = ( a[0][i+1] - a[0][i] ) / dt;
        vy = ( a[1][i+1] - a[1][i] ) / dt;
        velocity[i] = sqrt(vx*vx + vy*vy);
        speed += velocity[i];
    }

    speed /= (float)(SIZE - 1);
    for (i = 1; i < SIZE; i++) {
        printf("v[%d] = %.3f m/s\n", i, velocity[i-1]);
    }
    printf("mean velocity = %.3f", speed);
    _getch();
}

3. Массив используется как карта, где число 2 означает начало, а 3 - конец пути. Программа сначала находит координаты этих точек, после этого вычисляет расстояние Манхеттена (сколько нужно пройти по x и y от начала до конца) и расстояние по Евклиду (как гипотенузу прямоугольного треугольника).

#include <conio.h>
#include <stdio.h>
#include <math.h>

#define SIZE 5
#define START  2
#define FINISH 3

void main() {
    char field[SIZE][SIZE] = {
        {0, 0, 0, 0, 0},
        {2, 0, 0, 0, 0},
        {0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0},
        {0, 0, 0, 0, 3}
    };
    unsigned i, j;
    unsigned x, y;
    char xFound = 0;
    int X, Y;
    char XFound = 0;
    unsigned manhattanDist;
    float euclidDist;

    for (i = 0; i < SIZE; i++) {
        for (j = 0; j < SIZE; j++) {
            if (field[i][j] == START) {
                x = i;
                y = j;
                xFound = 1;
                if (XFound) {
                    break;
                }
            }
            if (field[i][j] == FINISH) {
                X = i;
                Y = j;
                XFound = 1;
                if (xFound) {
                    break;
                }
            }
        }
        if (xFound && XFound) {
            break;
        }
    }
    if (!(xFound && XFound)) {
        printf("Error: corrupted data\n");
        getch();
        exit(1);
    }

    printf("(x,y) = %d, %d\n(X,Y)= %d, %d\n", x, y, X, Y);

    manhattanDist = abs((int)(x-X)) + abs((int)(y-Y));
    //тоже самое, что и sqrt((x-X)*(x-X)+(y-Y)*(y-Y))
    x -= X;
    y -= Y;
    euclidDist = sqrt((float)(x*x + y*y));
    printf("Manhattan dist. = %d\nEuclid dist. = %.3f", manhattanDist, euclidDist);
    _getch();
}

4. Пользователь вводит 10 слов. Вывести слово с максимальной длиной. Программа внешне совершенно простая, единственная проблема - считывание и вывод слова. Так как слова храняться в двумерном массиве, то указатель на words[i][0] - это начало нового слова. Также не забываем об ограничении на длину при вводе.

#include <conio.h>
#include <stdio.h>

#define SIZE 10
#define MAX_LENGTH 128

void main() {
    //Массив хранит 10 слов максимум по 128 символов
    char words[SIZE][MAX_LENGTH];
    unsigned i, j, maxLength;
    //Так как длина слова ограничена 127 символами, то типа char хватит
    unsigned char counter[SIZE];

    for (i = 0; i < SIZE; i++) {
        //Считываем слова. words[i][0] - это символ, нам нужен
        //адрес, начиная с которого можно писать в массив
        fgets(&words[i][0], MAX_LENGTH - 1, stdin);
        j = 0;
        //Считаем длину слова
        while (words[i][j]) {
            j++;
        }
        counter[i] = j;
    }

    //Ищем слово с максимальной длиной
    maxLength = counter[0];
    j = 0;
    for (i = 1; i < SIZE; i++) {
        if (counter[i] > maxLength) {
            maxLength = counter[i];
            j = i;
        }
    }

    //Выводим слово на печать. При выводе строки
    //необходимо передавать указатель
    printf("%s", &words[j][0]);
    _getch();
}